#!/usr/bin/python
# -*- coding: utf-8 -*-
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
# 
# $Id: event.py 203 2009-08-16 20:40:53Z keith.dart $
#
#    Copyright (C)  Keith Dart <keith@kdart.com>
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.

"""
Interface to produce Linux event stream from ascii data.

"""

from pycopia.fsm import FSM
from pycopia import ascii

from pycopia import scheduler

# meta modifiers for tables
SHIFT = 0x200
ALT = 0x400
META = 0x800
COMPOSE = 0x1000

# from <linux/input.h>
# * Event types

EV_SYN = 0x00
EV_KEY = 0x01
EV_REL = 0x02
EV_ABS = 0x03
EV_MSC = 0x04
EV_SW = 0x05
EV_LED = 0x11
EV_SND = 0x12
EV_REP = 0x14
EV_FF = 0x15
EV_PWR = 0x16
EV_FF_STATUS = 0x17
EV_MAX = 0x1f

# * Synchronization events.

SYN_REPORT = 0
SYN_CONFIG = 1

# * Keys and buttons

KEY_RESERVED = 0
KEY_ESC = 1
KEY_1 = 2
KEY_2 = 3
KEY_3 = 4
KEY_4 = 5
KEY_5 = 6
KEY_6 = 7
KEY_7 = 8
KEY_8 = 9
KEY_9 = 10
KEY_0 = 11
KEY_MINUS = 12
KEY_EQUAL = 13
KEY_BACKSPACE = 14
KEY_TAB = 15
KEY_Q = 16
KEY_W = 17
KEY_E = 18
KEY_R = 19
KEY_T = 20
KEY_Y = 21
KEY_U = 22
KEY_I = 23
KEY_O = 24
KEY_P = 25
KEY_LEFTBRACE = 26
KEY_RIGHTBRACE = 27
KEY_ENTER = 28
KEY_LEFTCTRL = 29
KEY_A = 30
KEY_S = 31
KEY_D = 32
KEY_F = 33
KEY_G = 34
KEY_H = 35
KEY_J = 36
KEY_K = 37
KEY_L = 38
KEY_SEMICOLON = 39
KEY_APOSTROPHE = 40
KEY_GRAVE = 41
KEY_LEFTSHIFT = 42
KEY_BACKSLASH = 43
KEY_Z = 44
KEY_X = 45
KEY_C = 46
KEY_V = 47
KEY_B = 48
KEY_N = 49
KEY_M = 50
KEY_COMMA = 51
KEY_DOT = 52
KEY_SLASH = 53
KEY_RIGHTSHIFT = 54
KEY_KPASTERISK = 55
KEY_LEFTALT = 56
KEY_SPACE = 57
KEY_CAPSLOCK = 58
KEY_F1 = 59
KEY_F2 = 60
KEY_F3 = 61
KEY_F4 = 62
KEY_F5 = 63
KEY_F6 = 64
KEY_F7 = 65
KEY_F8 = 66
KEY_F9 = 67
KEY_F10 = 68
KEY_NUMLOCK = 69
KEY_SCROLLLOCK = 70
KEY_KP7 = 71
KEY_KP8 = 72
KEY_KP9 = 73
KEY_KPMINUS = 74
KEY_KP4 = 75
KEY_KP5 = 76
KEY_KP6 = 77
KEY_KPPLUS = 78
KEY_KP1 = 79
KEY_KP2 = 80
KEY_KP3 = 81
KEY_KP0 = 82
KEY_KPDOT = 83
KEY_103RD = 84

KEY_ZENKAKUHANKAKU = 85
KEY_102ND = 86
KEY_F11 = 87
KEY_F12 = 88
KEY_RO = 89
KEY_KATAKANA = 90
KEY_HIRAGANA = 91
KEY_HENKAN = 92
KEY_KATAKANAHIRAGANA = 93
KEY_MUHENKAN = 94
KEY_KPJPCOMMA = 95
KEY_KPENTER = 96
KEY_RIGHTCTRL = 97
KEY_KPSLASH = 98
KEY_SYSRQ = 99
KEY_RIGHTALT = 100
KEY_LINEFEED = 101
KEY_HOME = 102
KEY_UP = 103
KEY_PAGEUP = 104
KEY_LEFT = 105
KEY_RIGHT = 106
KEY_END = 107
KEY_DOWN = 108
KEY_PAGEDOWN = 109
KEY_INSERT = 110
KEY_DELETE = 111
KEY_MACRO = 112
KEY_MUTE = 113
KEY_VOLUMEDOWN = 114
KEY_VOLUMEUP = 115
KEY_POWER = 116
KEY_KPEQUAL = 117
KEY_KPPLUSMINUS = 118
KEY_PAUSE = 119

KEY_KPCOMMA = 121
KEY_HANGUEL = 122
KEY_HANJA = 123
KEY_YEN = 124
KEY_LEFTMETA = 125
KEY_RIGHTMETA = 126
KEY_COMPOSE = 127

KEY_STOP = 128
KEY_AGAIN = 129
KEY_PROPS = 130
KEY_UNDO = 131
KEY_FRONT = 132
KEY_COPY = 133
KEY_OPEN = 134
KEY_PASTE = 135
KEY_FIND = 136
KEY_CUT = 137
KEY_HELP = 138
KEY_MENU = 139
KEY_CALC = 140
KEY_SETUP = 141
KEY_SLEEP = 142
KEY_WAKEUP = 143
KEY_FILE = 144
KEY_SENDFILE = 145
KEY_DELETEFILE = 146
KEY_XFER = 147
KEY_PROG1 = 148
KEY_PROG2 = 149
KEY_WWW = 150
KEY_MSDOS = 151
KEY_COFFEE = 152
KEY_DIRECTION = 153
KEY_CYCLEWINDOWS = 154
KEY_MAIL = 155
KEY_BOOKMARKS = 156
KEY_COMPUTER = 157
KEY_BACK = 158
KEY_FORWARD = 159
KEY_CLOSECD = 160
KEY_EJECTCD = 161
KEY_EJECTCLOSECD = 162
KEY_NEXTSONG = 163
KEY_PLAYPAUSE = 164
KEY_PREVIOUSSONG = 165
KEY_STOPCD = 166
KEY_RECORD = 167
KEY_REWIND = 168
KEY_PHONE = 169
KEY_ISO = 170
KEY_CONFIG = 171
KEY_HOMEPAGE = 172
KEY_REFRESH = 173
KEY_EXIT = 174
KEY_MOVE = 175
KEY_EDIT = 176
KEY_SCROLLUP = 177
KEY_SCROLLDOWN = 178
KEY_KPLEFTPAREN = 179
KEY_KPRIGHTPAREN = 180

KEY_F13 = 183
KEY_F14 = 184
KEY_F15 = 185
KEY_F16 = 186
KEY_F17 = 187
KEY_F18 = 188
KEY_F19 = 189
KEY_F20 = 190
KEY_F21 = 191
KEY_F22 = 192
KEY_F23 = 193
KEY_F24 = 194

KEY_PLAYCD = 200
KEY_PAUSECD = 201
KEY_PROG3 = 202
KEY_PROG4 = 203
KEY_SUSPEND = 205
KEY_CLOSE = 206
KEY_PLAY = 207
KEY_FASTFORWARD = 208
KEY_BASSBOOST = 209
KEY_PRINT = 210
KEY_HP = 211
KEY_CAMERA = 212
KEY_SOUND = 213
KEY_QUESTION = 214
KEY_EMAIL = 215
KEY_CHAT = 216
KEY_SEARCH = 217
KEY_CONNECT = 218
KEY_FINANCE = 219
KEY_SPORT = 220
KEY_SHOP = 221
KEY_ALTERASE = 222
KEY_CANCEL = 223
KEY_BRIGHTNESSDOWN = 224
KEY_BRIGHTNESSUP = 225
KEY_MEDIA = 226
KEY_SWITCHVIDEOMODE = 227
KEY_KBDILLUMTOGGLE = 228
KEY_KBDILLUMDOWN = 229
KEY_KBDILLUMUP = 230
KEY_SEND = 231
KEY_REPLY = 232
KEY_FORWARDMAIL = 233
KEY_SAVE = 234
KEY_DOCUMENTS = 235
KEY_BATTERY = 236

KEY_UNKNOWN = 240

BTN_MISC = 0x100
BTN_0 = 0x100
BTN_1 = 0x101
BTN_2 = 0x102
BTN_3 = 0x103
BTN_4 = 0x104
BTN_5 = 0x105
BTN_6 = 0x106
BTN_7 = 0x107
BTN_8 = 0x108
BTN_9 = 0x109

BTN_MOUSE = 0x110
BTN_LEFT = 0x110
BTN_RIGHT = 0x111
BTN_MIDDLE = 0x112
BTN_SIDE = 0x113
BTN_EXTRA = 0x114
BTN_FORWARD = 0x115
BTN_BACK = 0x116
BTN_TASK = 0x117

BTN_JOYSTICK = 0x120
BTN_TRIGGER = 0x120
BTN_THUMB = 0x121
BTN_THUMB2 = 0x122
BTN_TOP = 0x123
BTN_TOP2 = 0x124
BTN_PINKIE = 0x125
BTN_BASE = 0x126
BTN_BASE2 = 0x127
BTN_BASE3 = 0x128
BTN_BASE4 = 0x129
BTN_BASE5 = 0x12a
BTN_BASE6 = 0x12b
BTN_DEAD = 0x12f

BTN_GAMEPAD = 0x130
BTN_A = 0x130
BTN_B = 0x131
BTN_C = 0x132
BTN_X = 0x133
BTN_Y = 0x134
BTN_Z = 0x135
BTN_TL = 0x136
BTN_TR = 0x137
BTN_TL2 = 0x138
BTN_TR2 = 0x139
BTN_SELECT = 0x13a
BTN_START = 0x13b
BTN_MODE = 0x13c
BTN_THUMBL = 0x13d
BTN_THUMBR = 0x13e

BTN_DIGI = 0x140
BTN_TOOL_PEN = 0x140
BTN_TOOL_RUBBER = 0x141
BTN_TOOL_BRUSH = 0x142
BTN_TOOL_PENCIL = 0x143
BTN_TOOL_AIRBRUSH = 0x144
BTN_TOOL_FINGER = 0x145
BTN_TOOL_MOUSE = 0x146
BTN_TOOL_LENS = 0x147
BTN_TOUCH = 0x14a
BTN_STYLUS = 0x14b
BTN_STYLUS2 = 0x14c
BTN_TOOL_DOUBLETAP = 0x14d
BTN_TOOL_TRIPLETAP = 0x14e

BTN_WHEEL = 0x150
BTN_GEAR_DOWN = 0x150
BTN_GEAR_UP = 0x151

KEY_OK = 0x160
KEY_SELECT  = 0x161
KEY_GOTO = 0x162
KEY_CLEAR = 0x163
KEY_POWER2 = 0x164
KEY_OPTION = 0x165
KEY_INFO = 0x166
KEY_TIME = 0x167
KEY_VENDOR = 0x168
KEY_ARCHIVE = 0x169
KEY_PROGRAM = 0x16a
KEY_CHANNEL = 0x16b
KEY_FAVORITES = 0x16c
KEY_EPG = 0x16d
KEY_PVR = 0x16e
KEY_MHP = 0x16f
KEY_LANGUAGE = 0x170
KEY_TITLE = 0x171
KEY_SUBTITLE = 0x172
KEY_ANGLE = 0x173
KEY_ZOOM = 0x174
KEY_MODE = 0x175
KEY_KEYBOARD = 0x176
KEY_SCREEN = 0x177
KEY_PC = 0x178
KEY_TV = 0x179
KEY_TV2 = 0x17a
KEY_VCR = 0x17b
KEY_VCR2 = 0x17c
KEY_SAT = 0x17d
KEY_SAT2 = 0x17e
KEY_CD = 0x17f
KEY_TAPE = 0x180
KEY_RADIO = 0x181
KEY_TUNER = 0x182
KEY_PLAYER = 0x183
KEY_TEXT = 0x184
KEY_DVD = 0x185
KEY_AUX = 0x186
KEY_MP3 = 0x187
KEY_AUDIO = 0x188
KEY_VIDEO = 0x189
KEY_DIRECTORY = 0x18a
KEY_LIST = 0x18b
KEY_MEMO = 0x18c
KEY_CALENDAR = 0x18d
KEY_RED = 0x18e
KEY_GREEN = 0x18f
KEY_YELLOW = 0x190
KEY_BLUE = 0x191
KEY_CHANNELUP = 0x192
KEY_CHANNELDOWN = 0x193
KEY_FIRST = 0x194
KEY_LAST = 0x195
KEY_AB = 0x196
KEY_NEXT = 0x197
KEY_RESTART = 0x198
KEY_SLOW = 0x199
KEY_SHUFFLE = 0x19a
KEY_BREAK = 0x19b
KEY_PREVIOUS = 0x19c
KEY_DIGITS = 0x19d
KEY_TEEN = 0x19e
KEY_TWEN = 0x19f

KEY_DEL_EOL = 0x1c0
KEY_DEL_EOS = 0x1c1
KEY_INS_LINE = 0x1c2
KEY_DEL_LINE = 0x1c3

KEY_FN = 0x1d0
KEY_FN_ESC = 0x1d1
KEY_FN_F1 = 0x1d2
KEY_FN_F2 = 0x1d3
KEY_FN_F3 = 0x1d4
KEY_FN_F4 = 0x1d5
KEY_FN_F5 = 0x1d6
KEY_FN_F6 = 0x1d7
KEY_FN_F7 = 0x1d8
KEY_FN_F8 = 0x1d9
KEY_FN_F9 = 0x1da
KEY_FN_F10 = 0x1db
KEY_FN_F11 = 0x1dc
KEY_FN_F12 = 0x1dd
KEY_FN_1 = 0x1de
KEY_FN_2 = 0x1df
KEY_FN_D = 0x1e0
KEY_FN_E = 0x1e1
KEY_FN_F = 0x1e2
KEY_FN_S = 0x1e3
KEY_FN_B = 0x1e4

KEY_MAX = 0x1ff

# * Relative axes

REL_X = 0x00
REL_Y = 0x01
REL_Z = 0x02
REL_RX = 0x03
REL_RY = 0x04
REL_RZ = 0x05
REL_HWHEEL = 0x06
REL_DIAL = 0x07
REL_WHEEL = 0x08
REL_MISC = 0x09
REL_MAX = 0x0f

# * Absolute axes

ABS_X = 0x00
ABS_Y = 0x01
ABS_Z = 0x02
ABS_RX = 0x03
ABS_RY = 0x04
ABS_RZ = 0x05
ABS_THROTTLE = 0x06
ABS_RUDDER = 0x07
ABS_WHEEL = 0x08
ABS_GAS = 0x09
ABS_BRAKE = 0x0a
ABS_HAT0X = 0x10
ABS_HAT0Y = 0x11
ABS_HAT1X = 0x12
ABS_HAT1Y = 0x13
ABS_HAT2X = 0x14
ABS_HAT2Y = 0x15
ABS_HAT3X = 0x16
ABS_HAT3Y = 0x17
ABS_PRESSURE = 0x18
ABS_DISTANCE = 0x19
ABS_TILT_X = 0x1a
ABS_TILT_Y = 0x1b
ABS_TOOL_WIDTH = 0x1c
ABS_VOLUME = 0x20
ABS_MISC = 0x28
ABS_MAX = 0x3f

# * Switch events

SW_LID = 0x00  # /* set = lid shut */
SW_TABLET_MODE = 0x01  # /* set = tablet mode */
SW_HEADPHONE_INSERT = 0x02  # /* set = inserted */
SW_MAX = 0x0f

# * Misc events

MSC_SERIAL = 0x00
MSC_PULSELED = 0x01
MSC_GESTURE = 0x02
MSC_RAW = 0x03
MSC_SCAN = 0x04
MSC_MAX = 0x07

# * LEDs

LED_NUML = 0x00
LED_CAPSL = 0x01
LED_SCROLLL = 0x02
LED_COMPOSE = 0x03
LED_KANA = 0x04
LED_SLEEP = 0x05
LED_SUSPEND = 0x06
LED_MUTE = 0x07
LED_MISC = 0x08
LED_MAIL = 0x09
LED_CHARGING = 0x0a
LED_MAX = 0x0f

# * Autorepeat values

REP_DELAY = 0x00
REP_PERIOD = 0x01
REP_MAX = 0x01

# * Sounds

SND_CLICK = 0x00
SND_BELL = 0x01
SND_TONE = 0x02
SND_MAX = 0x07

# * IDs.

ID_BUS = 0
ID_VENDOR = 1
ID_PRODUCT = 2
ID_VERSION = 3

BUS_PCI = 0x01
BUS_ISAPNP = 0x02
BUS_USB = 0x03
BUS_HIL = 0x04
BUS_BLUETOOTH = 0x05

BUS_ISA = 0x10
BUS_I8042 = 0x11
BUS_XTKBD = 0x12
BUS_RS232 = 0x13
BUS_GAMEPORT = 0x14
BUS_PARPORT = 0x15
BUS_AMIGA = 0x16
BUS_ADB = 0x17
BUS_I2C = 0x18
BUS_HOST = 0x19
BUS_GSC = 0x1A



# Values describing the status of an effect

FF_STATUS_STOPPED = 0x00
FF_STATUS_PLAYING = 0x01
FF_STATUS_MAX = 0x01



_VALUEMAP = {}
for name, value in globals().items():
    if name.startswith("KEY_"):
        shortname = name[4:]
        _VALUEMAP[shortname] = value
del name, value

_LETTERS = {}
for c in ascii.lowercase:
    val = globals()["KEY_%s" % c.upper()]
    _LETTERS[c] = val
del c

_DIGITS = {}
for c in ascii.digits:
    val = globals()["KEY_%s" % c]
    _DIGITS[c] = val
del c

# ascii codes that result from modifiers
# TODO add more ISO-8859-1 symbols
_KEYMAP = { # This reflects a en_US keyboard mapping.
 "'": KEY_APOSTROPHE,
 ',': KEY_COMMA,
 '-': KEY_MINUS,
 '.': KEY_DOT,
 '/': KEY_SLASH,
 ';': KEY_SEMICOLON,
 '=': KEY_EQUAL,
 '[': KEY_LEFTBRACE,
 ']': KEY_RIGHTBRACE,
# '\\': KEY_BACKSLASH, # handled specially, since it is an escape
 '`': KEY_GRAVE,
 '!': SHIFT | KEY_1,
 '"': SHIFT | KEY_APOSTROPHE,
 '#': SHIFT | KEY_3,
 '$': SHIFT | KEY_4,
 '%': SHIFT | KEY_5,
 '&': SHIFT | KEY_7,
 '(': SHIFT | KEY_9,
 ')': SHIFT | KEY_0,
 '*': SHIFT | KEY_8,
 '+': SHIFT | KEY_EQUAL,
 ':': SHIFT | KEY_SEMICOLON,
 '<': SHIFT | KEY_COMMA,
 '>': SHIFT | KEY_DOT,
 '?': SHIFT | KEY_SLASH,
 '@': SHIFT | KEY_2,
 '^': SHIFT | KEY_6,
 '_': SHIFT | KEY_MINUS,
 '{': SHIFT | KEY_LEFTBRACE,
 '|': SHIFT | KEY_BACKSLASH,
 '}': SHIFT | KEY_RIGHTBRACE,
 '~': SHIFT | KEY_GRAVE,
 '¥': SHIFT | ALT | KEY_5,
 '£': SHIFT | ALT | KEY_3,
}

class RelativeMotionGenerator(object):
    def __init__(self, device):
        self._device = device # EventDevice handler (only uses write() method).

    def MoveUp(self, ticks=1):
        self._device.write(EV_REL, REL_Y, -ticks)
        self._device.write(EV_SYN, 0, 0)

    def MoveDown(self, ticks=1):
        self._device.write(EV_REL, REL_Y, ticks)
        self._device.write(EV_SYN, 0, 0)

    def MoveLeft(self, ticks=1):
        self._device.write(EV_REL, REL_X, -ticks)
        self._device.write(EV_SYN, 0, 0)

    def MoveRight(self, ticks=1):
        self._device.write(EV_REL, REL_X, ticks)
        self._device.write(EV_SYN, 0, 0)


class AbsoluteMotionGenerator(object):
    def __init__(self, device):
        self._device = device # EventDevice handler

    def MoveTo(self, x, y):
        self._device.write(EV_ABS, ABS_X, x)
        self._device.write(EV_ABS, ABS_Y, y)


class KeyEventGenerator(object):
    """ASCII in, events out. Call an instance with a string."""

    def __init__(self, device, keymap=_KEYMAP):
        self._device = device # EventDevice handler (only uses write() method).
        self._symbolmapping = keymap
        self._init(keymap)
        self.reset()

    def reset(self):
        self._fsm.reset()
        self._ctrl = 0 # %C
        self._shift = 0 # %S
        self._alt = 0 # %A
        self._meta = 0 # %M
        self._compose = 0 # %O

    def __call__(self, text):
        for c in text:
            self._fsm.process(c)

    def _init(self, keymap):
        keysyms = "".join(keymap.keys())
        f = FSM(0)
        f.add_default_transition(self._error, 0)
        # key code names are in angle brackets
        f.add_transition_list(ascii.lowercase, 0, self._lower, 0)
        f.add_transition_list(ascii.uppercase, 0, self._upper, 0)
        f.add_transition_list(ascii.digits, 0, self._digit, 0)
        f.add_transition_list(keysyms, 0, self._symbols, 0)
        f.add_transition_list(ascii.control, 0, self._control, 0)
        f.add_transition(" ", 0, self._space, 0)
        # Any key name may use the "<NAME>" syntax (without the "KEY_")
        f.add_transition('<', 0, self._startkeycode, 2)
        f.add_transition_list(ascii.uppercase, 2, self._keyname, 2)
        f.add_transition('>', 2, self._keycode, 0)
        # slashes escape any special character.
        f.add_transition("\\", 0, None, 1)
        f.add_transition("\\", 1, self._backslash, 0)
        f.add_transition_list("tnrbe", 1, self._specials, 0)
        f.add_transition_list(keysyms, 1, self._symbols, 0)
        # percent signals meta transitions.
        f.add_transition("%", 0, None, 3)
        f.add_transition("%", 3, self._symbols, 0)
        f.add_transition_list("CcSsAaMmOo", 3, self._stickies, 0)
        self._fsm = f

    def _error(self, input_symbol, fsm):
        msg = 'Error: symbol: %s, state: %s' % (input_symbol, fsm.current_state)
        fsm.reset()
        raise ValueError(msg)

    def _startkeycode(self, c, fsm):
        fsm.keyname = ""

    def _keyname(self, c, fsm):
        fsm.keyname += c

    def _presskey(self, val):
        self._device.write(EV_KEY, val, 1)
        self._device.write(EV_KEY, val, 0)

    def _keycode(self, c, fsm):
        val = _VALUEMAP[fsm.keyname]
        self._presskey(val)
        scheduler.sleep(0.1)

    def _lower(self, c, fsm):
        val = _LETTERS[c]
        self._presskey(val)
        scheduler.sleep(0.1)

    def _upper(self, c, fsm):
        val = _LETTERS[c.lower()]
        self._device.write(EV_KEY, KEY_LEFTSHIFT, 1)
        self._presskey(val)
        self._device.write(EV_KEY, KEY_LEFTSHIFT, 0)
        scheduler.sleep(0.1)

    def _digit(self, c, fsm):
        val = _DIGITS[c]
        self._presskey(val)
        scheduler.sleep(0.1)

    def _backslash(self, c, fsm):
        self._presskey(KEY_BACKSLASH)
        scheduler.sleep(0.1)

    def _symbols(self, c, fsm):
        d = self._device
        val = self._symbolmapping[c]
        code = val & 0x1ff
        shifted = val & SHIFT
        alt = val & ALT
        meta = val & META
        compose = val & COMPOSE

        if compose:
            d.write(EV_KEY, KEY_COMPOSE, 1)
        if shifted:
            d.write(EV_KEY, KEY_LEFTSHIFT, 1)
        if alt:
            d.write(EV_KEY, KEY_LEFTALT, 1)
        if meta:
            d.write(EV_KEY, KEY_LEFTMETA, 1)
        self._presskey(code)
        if meta:
            d.write(EV_KEY, KEY_LEFTMETA, 0)
        if alt:
            d.write(EV_KEY, KEY_LEFTALT, 0)
        if shifted:
            d.write(EV_KEY, KEY_LEFTSHIFT, 0)
        if compose:
            d.write(EV_KEY, KEY_COMPOSE, 0)
        scheduler.sleep(0.1)

    def _specials(self, c, fsm):
        if c == "t":
            self._device.write(EV_KEY, KEY_TAB, 0)
        elif c == "n":
            self._device.write(EV_KEY, KEY_ENTER, 0)
        elif c == "r":
            self._device.write(EV_KEY, KEY_KPENTER, 0)
        elif c == "b":
            self._device.write(EV_KEY, KEY_BACKSPACE, 0)
        elif c == "e":
            self._device.write(EV_KEY, KEY_ESC, 0)
        scheduler.sleep(0.1)

    def _control(self, c, fsm):
        val = _LETTERS[chr(ord(c) | 0x60)]
        self._device.write(EV_KEY, KEY_LEFTCTRL, 1)
        self._presskey(val)
        self._device.write(EV_KEY, KEY_LEFTCTRL, 0)
        scheduler.sleep(0.1)

    def _space(self, c, fsm):
        self._presskey(KEY_SPACE)
        scheduler.sleep(0.1)

    # "sticky" modifiers. Explicitly turned on and off using a % prefix.
    def _stickies(self, c, fsm):
        if c == "S" and not self._shift:
            self._shift = 1
            self._device.write(EV_KEY, KEY_LEFTSHIFT, 1)
        elif c == "s" and self._shift:
            self._shift = 0
            self._device.write(EV_KEY, KEY_LEFTSHIFT, 0)

        elif c == "C" and not self._ctrl:
            self._ctrl = 1
            self._device.write(EV_KEY, KEY_LEFTCTRL, 1)
        elif c == "c" and self._ctrl:
            self._shift = 0
            self._device.write(EV_KEY, KEY_LEFTCTRL, 0)

        elif c == "A" and not self._alt:
            self._alt = 1
            self._device.write(EV_KEY, KEY_LEFTALT, 1)
        elif c == "a" and self._alt:
            self._alt = 0
            self._device.write(EV_KEY, KEY_LEFTALT, 0)

        elif c == "M" and not self._meta:
            self._meta = 1
            self._device.write(EV_KEY, KEY_LEFTMETA, 1)
        elif c == "m" and self._meta:
            self._meta = 0
            self._device.write(EV_KEY, KEY_LEFTMETA, 0)

        elif c == "O" and not self._compose:
            self._compose = 1
            self._device.write(EV_KEY, KEY_COMPOSE, 1)
        elif c == "o" and self._compose:
            self._compose = 0
            self._device.write(EV_KEY, KEY_COMPOSE, 0)

        scheduler.sleep(0.1)



# run module as root like this:
# python -i event.py
if __name__ == "__main__":
    from pycopia.OS import Input

    class MockDevice(object):
        def write(self, evtype, code, value):
            print "%s %s %s" % (evtype, code, value)
        def close(self):
            pass

    #fo = MockDevice()
    fo = Input.EventDevice()
    fo.find(name="keyboard")
    g = KeyEventGenerator(fo)
    scheduler.sleep(2)
    expected = (ascii.lowercase + "\n" + ascii.uppercase + "\n" + ascii.digits + "\n" +
        r"""!"#$&'()*+,-./:;<=>?@[\]^_`{|}~""" + "\n" + "ab%c\tDEF\tghi%\n" )
    g('sent = """')
    g(ascii.lowercase)
    g("\n")
    g(ascii.uppercase)
    g("\n")
    g(ascii.digits)
    g("\n")
    # You have to slash escape the "<" and \ itself.
    g( r"""!"#$&'()*+,-./:;\<=>?@[\\]^_`{|}~""" )
    g("\n")
    g("ab\%c<TAB>%Sdef%s%Ci%cghi%%" )
    g("\n")
    g('"""\n')
    g('sent == expected\n')
    fo.close()

